# TabGroup - TabGroup is a group of tabs. It draws tab bar.
# Copyright (C) 2007 Lauri Taimila
# 
# Entertainer is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# Entertainer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

__licence__ = "GPLv2"
__copyright__ = "2008, Lauri Taimila"
__author__ = "Lauri Taimila <lauri@taimila.com>"

import pango
import cairo
import clutter
from clutter.cluttercairo import CairoTexture

from frontend.user_event import UserEvent

class TabGroup(clutter.Group):
    """
    This is a top level container for tabs.
    
    This widget contains graphics for tab bar, but content of the tabs are
    separated into Tab objects.
    """
    
    def __init__(self, width, height, color):
        """
        Initialize empty tab group.
        @param width: Width of the tab bar in percents (0-1)
        @param height: height of the tab bar in percents (0-1)
        @param color: clutter.Color - Color of the active tab label
        """
        clutter.Group.__init__(self)
        
        self.active = False
        
        # Size
        stage = clutter.Stage()
        self.stage_w = stage.get_width()
        self.stage_h = stage.get_height()
        self.width_factor = width
        self.height_factor = height
        
        # Label text colors
        self.selected_color = color
        self.unselected_color = (color.red, color.green, color.blue, 96)
        
        # Size of the tab bar (This includes label texts)
        self.rect = clutter.Rectangle()
        self.rect.set_size(int(self.stage_w * self.width_factor), int(self.stage_h * self.height_factor))
        self.rect.set_color(clutter.Color(255,0,0,0))
        self.rect.show()
        self.add(self.rect)
        
        self.tabs = []
        self.labels = []
        self.timelines = [] # Contains tuples (timeline, alpha, behaviour)
        self.current_tab = None
    
    def __len__(self):
        """
        Get TagGroup length
        @return: Integer, Number of tabs in this TabGroup
        """
        return len(self.tabs)
    
    def resize_widget(self, stage_size):
        """
        Update widget size for new stage size. This is called when stage size
        has changed.
        @param stage_size: Tuple with width and height in pixels (1028x1024)
        """
        stage = clutter.Stage()
        self.stage_w = stage.get_width()
        self.stage_h = stage.get_height()
    
    def set_active(self, boolean):
        """
        Set widget status active / inactive
        @param boolean: True to set active, False to set inactive
        """
        self.active = boolean
        if boolean:
            self.set_opacity(255)
        else:
            self.set_opacity(128)
        
    def is_active(self):
        """
        Is this widget active?
        @return: boolean
        """
        return self.active
    
    def hide(self):
        """
        Override clutter.Actor function
        """
        #Stop processing here (animations etc)
        clutter.Group.hide(self)
        
    def show(self):
        """
        Override clutter.Actor function
        """
        # Start processing visible tab. Activate animations etc
        clutter.Group.show(self)
        
    def add_tab(self, tab):
        """
        Add new tab to this tab group.
        @param tab: Tab object to be added
        """
        self.tabs.append(tab)
        
        # Set first tab active
        if self.current_tab == None:
            self.current_tab = 0
            self.tabs[0].show_tab()
            
        if self.get_property('visible') == True:
            pass # Animate
        else:
            tab_title = clutter.Label()
            tab_title.set_position(0,0)
            tab_title.set_font_name("Sans " + str(0.00001) + "px")
            tab_title.set_color(self.selected_color)
            tab_title.set_text(tab.get_title())
            tab_title.show()
            self.add(tab_title)
            self.labels.append(tab_title)
            self._reposition_labels()
            
        timeline = clutter.Timeline(25,60)
        alpha = clutter.Alpha(timeline, clutter.smoothstep_inc_func)
        behaviour = clutter.BehaviourOpacity(255, 96, alpha)
        behaviour.apply(tab_title)
        self.timelines.append((timeline,alpha,behaviour))
        
    def remove_tab(self, tab):
        """
        Remove tab from this tab group
        @param tab: Tab object to be removed
        """
        self.tabs.remove(tab)
        if len(self.tabs) == 0:
            self.current_tab = None
    
    def _reposition_labels(self):
        """
        This is called every time we need to calculate new positions to the labels.
        This is when we add or remove tabs from this TabGroup. This method
        calculates label positions and sizes and updates UI accordingly
        """
        self.font_size = self.get_height() * 0.3
        count = len(self.labels)
        width = int(self.stage_w * self.width_factor)
                
        for i, label in enumerate(self.labels):
            if i != self.current_tab:
                label.set_color(self.unselected_color)
            label.set_font_name("Sans " + str(self.font_size) + "px")
            label.set_anchor_point_from_gravity(clutter.GRAVITY_CENTER)
            label.set_y(int(self.get_height() / 2))
            label.set_x((width / len(self.labels) * (i + 1)) - (width / count / 2))
            label.set_line_wrap(False)
            label.set_ellipsize(pango.ELLIPSIZE_END)
        
    def get_tabs(self):
        """
        Get all tabs from TabGroup.
        @return: List of Tab objects or empty list if there is no tabs
        """
        return self.tabs
    
    def get_current_tab(self):
        """
        Get currently active Tab
        @return: Tab object
        """
        return self.tabs[self.current_tab]
    
    def get_tab_by_title(self, title):
        """
        Get Tab object by it's title.
        @param title: String title of the Tab
        @return: Tab object or None if not found
        """
        index = -1;
        for i, label in enumerate(self.labels):
            if label.get_text() == title:
                index = i
                break
        if index != -1:
            return self.tabs[index]
        else:
            return None
    
    def get_tab_by_index(self, index):
        """
        Get Tab object from specific index.
        @param index: Integer 0 - len(TabGroup)
        @return: Tab object from that index
        """
        return self.tabs[index]
    
    def _switch_tab_to_left(self):
        """
        Switch one tab to left if possible. Do nothing if there is no tab
        """
        if self.current_tab > 0 and self.current_tab is not None:
            self.current_tab = self.current_tab - 1
            
            # Animate labels
            self.timelines[self.current_tab + 1][0].set_direction(clutter.TIMELINE_FORWARD)
            self.timelines[self.current_tab + 1][0].start()
            self.timelines[self.current_tab][0].set_direction(clutter.TIMELINE_BACKWARD)
            self.timelines[self.current_tab][0].start()
            
            # Change visible tab
            self.tabs[self.current_tab + 1].hide_tab()
            self.tabs[self.current_tab].show_tab()
        
    def _switch_tab_to_right(self):
        """
        Switch one tab to right if possible. Do nothing if there is no tab  
        """
        if self.current_tab  < len(self.tabs) - 1 and self.current_tab is not None:
            self.current_tab = self.current_tab + 1
            
            # Animate labels
            self.timelines[self.current_tab - 1][0].set_direction(clutter.TIMELINE_FORWARD)
            self.timelines[self.current_tab - 1][0].start()
            self.timelines[self.current_tab][0].set_direction(clutter.TIMELINE_BACKWARD)
            self.timelines[self.current_tab][0].start()
            
            # Change visible tab
            self.tabs[self.current_tab - 1].hide_tab()
            self.tabs[self.current_tab].show_tab()
            
    def handle_user_event(self, event):
        """ 
        Handle screen specific user events
        @param event: UserEvent object
        """
        if self.active:
            type = event.get_type()
            if type == UserEvent.NAVIGATE_LEFT:
                self._switch_tab_to_left()
            elif type == UserEvent.NAVIGATE_RIGHT:
                self._switch_tab_to_right()
            elif type == UserEvent.NAVIGATE_DOWN and self.tabs[self.current_tab].can_activate():
                self.set_active(False)
                self.tabs[self.current_tab].set_active(True)
        else:
            result = self.tabs[self.current_tab].handle_user_event(event)
            if result:
                self.set_active(True)
                self.tabs[self.current_tab].set_active(False)
    
    def _create_tab_graphics(self):
        """
        Create CairoTexture for tab bar.
        @return: cluttercairo.CairoTexture
        """
        width = 100
        height = 40
        texture = ClutterCairo(width, height)
        context = texture.cairo_create()
        context.set_line_width(0.1)
        context.set_source_rgba(self.selected_color[0], self.selected_color[1], self.selected_color[2], 0.8)
        context.line_to(1, 1)
        context.stroke()
        del context